#include "MyGlWindow.h"
#include "camera.h"
#include "CubeLampProg.h"
#include "CubeProg.h"
#include "TextureCubeProg.h"
#include <QOpenGLBuffer>
#include <QOpenGLFunctions_3_3_Core>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QDebug>


static GLfloat  vertices[] = {
    // positions          // vertex norms
     0.5f,  0.5f,  0.5f,    0.0f,  0.0f,  1.0f,     // front
    -0.5f,  0.5f,  0.5f,    0.0f,  0.0f,  1.0f,
    -0.5f, -0.5f,  0.5f,    0.0f,  0.0f,  1.0f,
    -0.5f, -0.5f,  0.5f,    0.0f,  0.0f,  1.0f,
     0.5f, -0.5f,  0.5f,    0.0f,  0.0f,  1.0f,
     0.5f,  0.5f,  0.5f,    0.0f,  0.0f,  1.0f,

     0.5f, -0.5f, -0.5f,	0.0f,  0.0f, -1.0f,     // back
    -0.5f, -0.5f, -0.5f,	0.0f,  0.0f, -1.0f,
    -0.5f,  0.5f, -0.5f,	0.0f,  0.0f, -1.0f,
    -0.5f,  0.5f, -0.5f,	0.0f,  0.0f, -1.0f,
     0.5f,  0.5f, -0.5f,	0.0f,  0.0f, -1.0f,
     0.5f, -0.5f, -0.5f,	0.0f,  0.0f, -1.0f,

     0.5f,  0.5f,  0.5f,    0.0f,  1.0f,  0.0f,     // top
     0.5f,  0.5f, -0.5f,    0.0f,  1.0f,  0.0f,
    -0.5f,  0.5f, -0.5f,    0.0f,  1.0f,  0.0f,
    -0.5f,  0.5f, -0.5f,    0.0f,  1.0f,  0.0f,
    -0.5f,  0.5f,  0.5f,    0.0f,  1.0f,  0.0f,
     0.5f,  0.5f,  0.5f,    0.0f,  1.0f,  0.0f,

     0.5f, -0.5f,  0.5f,    0.0f, -1.0f,  0.0f,     // buttom
     -0.5f, -0.5f,  0.5f,   0.0f, -1.0f,  0.0f,
     -0.5f, -0.5f, -0.5f,   0.0f, -1.0f,  0.0f,
     -0.5f, -0.5f, -0.5f,   0.0f, -1.0f,  0.0f,
     0.5f, -0.5f, -0.5f,    0.0f, -1.0f,  0.0f,
     0.5f, -0.5f,  0.5f,    0.0f, -1.0f,  0.0f,

     -0.5f,  0.5f,  0.5f,   -1.0f,  0.0f,  0.0f,     // left
     -0.5f,  0.5f, -0.5f,   -1.0f,  0.0f,  0.0f,
     -0.5f, -0.5f, -0.5f,   -1.0f,  0.0f,  0.0f,
     -0.5f, -0.5f, -0.5f,   -1.0f,  0.0f,  0.0f,
     -0.5f, -0.5f,  0.5f,   -1.0f,  0.0f,  0.0f,
     -0.5f,  0.5f,  0.5f,   -1.0f,  0.0f,  0.0f,

     0.5f,  0.5f,  0.5f,    1.0f,  0.0f,  0.0f,     // right
     0.5f, -0.5f,  0.5f,    1.0f,  0.0f,  0.0f,
     0.5f, -0.5f, -0.5f,    1.0f,  0.0f,  0.0f,
     0.5f, -0.5f, -0.5f,    1.0f,  0.0f,  0.0f,
     0.5f,  0.5f, -0.5f,    1.0f,  0.0f,  0.0f,
     0.5f,  0.5f,  0.5f,    1.0f,  0.0f,  0.0f
};

MyGlWindow::MyGlWindow()
    : MY_GL_FUNC(nullptr)
    , vbo(nullptr)
    , camera(nullptr)
    , firstMouse(true)
    , lampProg(nullptr)
    , materialProg(nullptr)
    , textureProg(nullptr)
{}

void MyGlWindow::initializeGL()
{
    MY_GL_FUNC = context()->versionFunctions<QOpenGLFunctions_3_3_Core>();

    if (! MY_GL_FUNC) {
        qCritical("NO support for QOpenGLFunctions_3_3_Core!");
        ::exit(0);
    }

    connect(context(), SIGNAL(aboutToBeDestroyed()), SLOT(teardownGL()));
    MY_GL_FUNC->initializeOpenGLFunctions();
    MY_GL_FUNC->glEnable(GL_CULL_FACE);
    MY_GL_FUNC->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    bool rc = false;

    // create vertex buffer objects (VBO)
    vbo = new QOpenGLBuffer;
    rc = vbo->create();
    Q_ASSERT(rc);

    rc = vbo->bind();
    Q_ASSERT(rc);

    //vbo->setUsagePattern(QOpenGLBuffer::StaticDraw);
    qDebug() << Q_FUNC_INFO << "VBO usage pattern" << vbo->usagePattern();
    vbo->allocate(vertices, sizeof(vertices));

    lampProg = new CubeLampProg(this);
    lampProg->initializeGL(context(), vertices, sizeof(vertices));
    lampProg->setScale(0.05f, 0.05f, 0.05f);
    lampProg->setTranslation(1.2f, 1.0f, 2.0f);
    lampProg->setLampColor(1.0f, 1.0f, 1.0f);

    connect(lampProg, SIGNAL(dataChanged()), SLOT(update()));

    materialProg = new CubeProg(this);
    materialProg->initializeGL(context(), vertices, sizeof(vertices));

    Material material;
    material.ambient = QVector3D(1.0f, 0.5f, 0.31f);
    material.diffuse = QVector3D(1.0f, 0.5f, 0.31f);
    material.specular = QVector3D(1.0f, 0.5f, 0.31f);
    material.shininess = 32.0f;

    materialProg->setMaterial(material);
    materialProg->setTranslation(-0.5f, 0.0f, 0.0f);
    materialProg->setScale(0.6f, 0.6f, 0.6f);
    connect(materialProg, SIGNAL(dataChanged()), SLOT(update()));

    vbo->release();

    textureProg = new TextureCubeProg(this);
    textureProg->initializeGL(context(), nullptr, 0);
    textureProg->setTranslation(1.0f, 0.0f, 0.0f);
    connect(textureProg, SIGNAL(dataChanged()), SLOT(update()));

    camera = new Camera(QVector3D(0.0f, 0.0f, 3.0f));
}

void MyGlWindow::paintGL()
{
    MY_GL_FUNC->glClear(GL_COLOR_BUFFER_BIT);

    QMatrix4x4  projection;
    projection.perspective(camera->fov, width() / float(height()), 0.1f, 100.0f);

    lampProg->paintGL(*camera, projection);

    Light light;
    light.position = lampProg->translation();
    light.ambient = lampProg->lampColor() * 0.2f;
    light.diffuse = lampProg->lampColor() * 0.5f;
    light.specular = lampProg->lampColor();

    materialProg->paintGL(*camera, projection, light);
    textureProg->paintGL(*camera, projection, light);
}

void MyGlWindow::resizeGL(int w, int h)
{
    Q_UNUSED(w)
    Q_UNUSED(h)

    firstMouse = true;
}

void MyGlWindow::keyPressEvent(QKeyEvent *ev)
{
    switch (ev->key()) {

    case Qt::Key_W:
        camera->move(Camera::FORWARD);
        update();
        break;

    case Qt::Key_S:
        camera->move(Camera::BACKWARD);
        update();
        break;

    case Qt::Key_A:
        camera->move(Camera::LEFT);
        update();
        break;

    case Qt::Key_D:
        camera->move(Camera::RIGHT);
        update();
        break;

    default:
        ev->ignore();
        break;
    }
}

void MyGlWindow::mouseMoveEvent(QMouseEvent *ev)
{
    if (firstMouse) {
        lastMousePos = ev->pos();
        firstMouse = false;
        return;
    }

    QPoint diff = ev->pos() - lastMousePos;

    if (QPoint::dotProduct(diff, diff) > 8 * 8) {
        // ignore the case in which the mouse move drastically
        firstMouse = true;
        return;
    }

    float xoffset = ev->x() - lastMousePos.x();
    float yoffset = lastMousePos.y() - ev->y();

    lastMousePos = ev->pos();

    camera->rotate(xoffset, yoffset);
    update();
}

void MyGlWindow::wheelEvent(QWheelEvent *ev)
{
    if (ev->delta() > 0) {

        camera->zoomIn();
        update();

    } else if (ev->delta() < 0) {

        camera->zoomOut();
        update();

    } else
        ev->ignore();
}

void MyGlWindow::teardownGL()
{
    delete textureProg;
    textureProg = nullptr;

    delete materialProg;
    materialProg = nullptr;

    delete lampProg;
    lampProg = nullptr;

    delete camera;
    camera = nullptr;

    delete vbo;
    vbo = nullptr;
}

QString MyGlWindow::contextInformation() const
{
  QString glType;
  QString glVersion;
  QString glProfile;

  // Get Version Information
  glType = (context()->isOpenGLES()) ? "OpenGL ES" : "OpenGL";
  glVersion = reinterpret_cast<const char*>(MY_GL_FUNC->glGetString(GL_VERSION));
//  glVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));

  // Get Profile Information
#define CASE(c) case QSurfaceFormat::c: glProfile = #c; break
  switch (format().profile())
  {
    CASE(NoProfile);
    CASE(CoreProfile);
    CASE(CompatibilityProfile);
  }
#undef CASE

   return glType + "\t" + glVersion + "\t" + glProfile;
}
